<?php 
  // controller.php
  // SimpleVC
  //     Controller loader for SimpleVC
  // June 12, 2010
  
  // The secret sauce.  Instant controller controller, just add controllers!
  
  // You'll need a rewrite rule to use this.  The rewrite rule is:
  // ^(.*)$ => controller.php?__path=$1
  
  // Or, provide separate variables in a rewrite rule:
  // __path = The controller, relative to the Controller directory
  // __querystring = The query string of the request, rewritten as desired
  // __output = The desired output {yaml|js|json|xml|sphp|html}
  
require_once 'config.inc';
require_once VC_LIB_PATH . 'SimpleController.class.inc';

// Set the default error handler
set_error_handler('handleErrors');
set_exception_handler('handleExceptions');

// Check to see if a path is set, or if we need to detect it.
if (isset($_GET['__path'])) {
    if(strstr($_GET['__path'], '?')) {
        $bits = explode('?', $_GET['__path']);
        $controller = $bits[0];
        $queryString = $bits[1];
        
        // Now load $_GET with the query string
        $get = explode('&', $queryString);
        foreach($get as $param) {
            $paramBits = explode('=', $param);
            $_GET[$paramBits[0]] = urldecode($paramBits[1]);
        }
    } else {
        $controller = $_GET['__path'];
    }
} else {
    displayError("No Request Path Set.", $code = 500);
}

// Check for __output, or strip off any extensions, and keep 'em.
if (isset($_GET['__output'])) {
  $extension = $_GET['__output'];
} else {
  $bits = explode('.', $controller);
  if(count($bits) > 1) {
	  $controller = $bits[0];
	  $extension  = $bits[1];
  } else {
	  $extension = 'html';
  }
}

// Now, attempt to load the requested controller.
$controllerFile = VC_CONTROLLER_PATH . $controller . '.class.inc';
if(is_file($controllerFile)) {
    // The method is the function which will be called on a method.
    $method = strtolower($_SERVER['REQUEST_METHOD']);
    
    // A controller's class is "upper case words separated by underscores."
    // So the controller for admin/users would be "Admin_Users"
    $controllerClass = str_replace('/', ' ', $controller);
    $controllerClass = trim($controllerClass);
    $controllerClass = ucwords($controllerClass);
    $controllerClass = str_replace(' ', '_', $controllerClass);

    // Include the controller, instantiate the controller class, and call the
    // method method.
    include_once($controllerFile);
    $class = new $controllerClass;
    if ($class->$method() === false) {
        displayError("{$controllerClass} Not Implemented", 501);
    }
    
    // See if we're expecting a json, yaml, xml, php, or html response.
    // Response type will either be in a get variable "__response_type"
    // or the extension on the request.  'template powered' response is default.
    switch (strtolower($extension)) {
        case 'sphp':
            header('Content-Type: text/plain');
            echo serialize($class->getTemplateValues());
            break;
        case 'yaml':
            if(function_exists('yaml_emit')) {
                header('Content-Type: text/yaml');
                echo yaml_emit($class->getTemplateValues());
            } else {
                displayError("YAML Output Not Implemented", 501);
            }
            break;
        case 'js':
            if(isset($_GET['__function'])) {
                // Wrap the json in a js function and set type as javascript!
                header('Content-Type: text/javascript');
                
                $json = json_encode($class->getTemplateValues());
                echo "function {$_GET['__function']}() {$json}";
                break;
            }
            // They didn't set a function, so cascade into regular json.
        case 'json':
            header('Content-Type: application/json', true);
            echo json_encode($class->getTemplateValues());
            break;
        case 'xml':
            require_once VC_LIB_PATH . 'XMLSerializer.class.php';
            echo XMLSerializer::generateValidXmlFromArray($class->getTemplateValues());
            break;
        default:
            // At this point, PHP's variable space only contains what the controller
            // assigned for the template.  Time to include the template.
            $templatePath = VC_TEMPLATE_PATH . $controller . '.tpl.php';
            if(is_file($templatePath)) {
                
                // Clean out the variable space, except for _POST and _GET
                $definedVars = get_defined_vars();
                foreach($definedVars as $varName => $varValue) {
                    if ($varName != '_POST' &&
                        $varName != '_GET' &&
                        $varName != 'class' &&
                        $varName != 'controller' &&
                        $varName != 'templatePath') {
                        unset($$varName);
                    }
                }
                $viewVars = $class->getTemplateValues();
                foreach($viewVars as $varName => $varValue) {
                    $$varName = $varValue;
                }
                unset($definedVars); unset($viewVars); unset($varName); unset($varValue);
                unset($class); unset($controller);
                require_once($templatePath);
            } else {
                // A very simple "html template".  It's just print_r with pre.
				echo '<pre>';
				print_r($class->getTemplateValues());
            }
            die();
    }
} else {
	throw new Exception("No controller set for this path.");
}

function handleErrors($errno, $errstr, $errfile, $errline) {
    // HACK HACK HACK
    // I can't find documented anywhere how to turn off "Undefined Variable"
    // notices.  So I detect for error number "8" (Which I've deduced is the
    // error number for Undefined Variable) and skip it here.
    if ($errno == 8) { return true; }
    // I really just want this activated around the template;  if you've got an
    // undefined variable in a template I just want it ignored.  This is a
    // todo I guess, but I'd rather do it the "right way."
    errorTicket("{$errstr} on {$errline}:{$errfile}", debug_string_backtrace());
}

function handleExceptions($exception) {
    $message = $exception->getMessage();
    $file = $exception->getFile();
    $line = $exception->getLine();
    $trace = $exception->getTraceAsString();
    errorTicket("Exception '{$message}' on {$line}:{$file}.", $trace);
}

// From comment on http://www.php.net/manual/en/function.debug-print-backtrace.php
function debug_string_backtrace() { 
    ob_start(); 
    debug_print_backtrace(); 
    $trace = ob_get_contents(); 
    ob_end_clean(); 

    // Remove first item from backtrace as it's this function which 
    // is redundant. 
    $trace = preg_replace ('/^#0\s+' . __FUNCTION__ . "[^\n]*\n/", '', $trace, 1); 

    // Renumber backtrace items. 
    $trace = preg_replace ('/^#(\d+)/me', '\'#\' . ($1 - 1)', $trace); 

    return $trace; 
}

function errorTicket($error, $details = null) {
    $meditationId = uniqid();
    logger(VC_APP_NAME . " Guru Meditation {$meditationId}", VC_LOGGER_ERROR);
    switch(VC_ERROR_HANDLER) {
        case 'file':
            $status = ticketFile($meditationId, $error, $details);
            break;
        case 'display':
            displayError($meditationId, $error, $details, 501);
            break;
        default:
            header("Error Handler Not Defined", true, 501);
            echo "<h1>Error Handler Not Defined</h1>";
            echo "<p><strong>" . VC_APP_NAME . "</strong></p>";
            echo "<p><em>SimpleVC Error Handler</em></p>";
            die();
    }
    echo "<h1>There was an error processing this request.</h1>";
    echo "<p><strong>" . VC_APP_NAME . " Guru Meditation {$meditationId}</strong></p>";
    if(isset($status)) {
        echo "<p>{$status}</p>";
    }
}

function displayError($meditationId, $error, $details=null, $code = 501) {
        header("{$code} {$error}", true, $code);
        echo "<h1>{$error}</h1>";
        echo "<p><strong>" . VC_APP_NAME . " Guru Meditation {$meditationId}</strong></p>";
        echo "<p><em>SimpleVC Request State:</em></p>";
        echo "<pre>";
        if(!is_null($details)) {
            echo "<p>{$details}</p>";
        }
        echo "<h2>Get</h2>";
        print_r($_GET);
        echo "<h2>Post</h2>";
        print_r($_POST);
        echo "<h2>Server</h2>";
        print_r($_SERVER);
}

function ticketFile($meditationId, $error, $details) {
    if(is_dir(VC_TICKET_PATH) && is_writable(VC_TICKET_PATH)) {
        $ticket_path = VC_TICKET_PATH . $meditationId . '.txt';
        
        $ticket_contents = "{$error}\n\n{$details}";
        
        $ticket_contents .= "\n\nGET\n---\n";
        $ticket_contents .= print_r($_GET, true);
        $ticket_contents .= "\n\nPOST\n----\n";
        $ticket_contents .= print_r($_POST, true);
        $ticket_contents .= "\n\nSERVER\n------\n";
        $ticket_contents .= print_r($_SERVER, true);
        
        file_put_contents($ticket_path, $ticket_contents);
    } else {
        return "Unable to write ticket.";
    }
}

function logger($message, $level = VC_LOGGER_NOTICE) {
    if(is_writable(VC_LOGGER_PATH)) {
        $fd = fopen(VC_LOGGER_PATH . VC_LOGGER_NAME, "a");
        fwrite($fd, "{$level}: {$message}\n");
        fclose($fd);
    }
}